﻿$import( "SmitearWebGL.Core.ResourceItem" );
$import( "SmitearWebGL.Utils.Md5" );

SmitearWebGL.Resources.Shader = SmitearWebGL.Core.ResourceItem.extend(
{
	/******** constructor ********/

	init : function()
	{
		this._super();
		this._registClass( "SmitearWebGL.Resources.Shader" );

		/******** public property ********/

		this.glProgram = null;
		this.uniforms = null;
		this.attributes = null;

		/******** private property ********/

		this._vertexShaderLoaded = false;
		this._pixelShaderLoaded = false;
		this._vsShader = null;
		this._psShader = null;
	}
	,
	
	/******** public method ********/
	
	isProgramReady : function()
	{
		return this.glProgram != null;
	}
	,
	loadVertexShaderFromString : function( shaderString )
	{
		this.loadShaderFromString( shaderString, this.context.gl.VERTEX_SHADER );
	}
	,
	loadPixelShaderFromString : function( shaderString )
	{
		this.loadShaderFromString( shaderString, this.context.gl.FRAGMENT_SHADER );
	}
	,
	loadShaderFromString : function( shaderString, type )
	{
		var shaderCache;
		
		if ( type == this.context.gl.VERTEX_SHADER )
		{
			shaderCache = SmitearWebGL.Resources.Shader.vsShaderCache;
		}
		else if ( type == this.context.gl.FRAGMENT_SHADER )
		{
			shaderCache = SmitearWebGL.Resources.Shader.psShaderCache;
		}
		else
		{
			$THROW( false, "Shader type error." );
		}
		
		var hash = SmitearWebGL.Utils.Md5.hex_md5( shaderString );
		var shader = shaderCache[hash];
		
		if ( ! shader )
		{
			shader = this.context.gl.createShader( type );
			shader.reference = 1;
			this.context.gl.shaderSource( shader, shaderString );
			this.context.gl.compileShader( shader );
			
			if ( ! this.context.gl.getShaderParameter( shader, this.context.gl.COMPILE_STATUS ) )
			{
				$THROW( false, "An error occurred compiling the shaders:\n" + this.context.gl.getShaderInfoLog( shader ) );
				this.context.gl.deleteShader( shader );
				shader = null;
				return;
			}
			
			shaderCache[hash] = shader;
		}
		else
			shader.reference ++;
		
		if ( type == this.context.gl.VERTEX_SHADER )
		{
			this._vsShader = shader;
			this._vertexShaderLoaded = true;
		}
		else if ( type == this.context.gl.FRAGMENT_SHADER )
		{
			this._psShader = shader;
			this._pixelShaderLoaded = true;
		}
		
		this._bindAttributesAndLink();
	}
	,
	loadVertexShaderFromFile : function( url )
	{
		var self = this;
		
		var req = new XMLHttpRequest();
		var req = new XMLHttpRequest();
		if( req )
		{
			req.overrideMimeType( "text/xml" );
			req.onreadystatechange = function()
			{
				if( this.readyState  == 4 )
				{
					if( this.status  == 200 || this.status == 0 )
					{
						self.loadVertexShaderFromString( this.responseText );
					}
					else
					{
						$THROW( false, errorThrown );
					}
				}
			}
			;
			req.open( "GET", url, false );
			req.send( "" );
		}
	}
	,
	loadPixelShaderFromFile : function( url )
	{
		var self = this;
		
		var req = new XMLHttpRequest();
		var req = new XMLHttpRequest();
		if( req )
		{
			req.overrideMimeType( "text/xml" );
			req.onreadystatechange = function()
			{
				if( this.readyState  == 4 )
				{
					if( this.status  == 200 || this.status == 0 )
					{
						self.loadPixelShaderFromString( this.responseText );
					}
					else
					{
						$THROW( false, errorThrown );
					}
				}
			}
			;
			req.open( "GET", url, false );
			req.send( "" );
		}
	}
	,

	/******** private method ********/

	_bindAttributesAndLink : function()
	{
		if ( this._vertexShaderLoaded && this._pixelShaderLoaded )
		{
			var programCache = SmitearWebGL.Resources.Shader.programCache;
			
			for( var i = 0; i < programCache.length; i ++ )
			{
				if( programCache[i].fShader === this._psShader && programCache[i].vShader === this._vsShader )
				{
					this.glProgram = programCache[i].program;
					this.glProgram.reference ++;
					this._getUniforms();
					this._getAttributes();
					
					return;
				}
			}
			
			this.glProgram = this.context.gl.createProgram();
			this.glProgram.reference = 1;
			this.context.gl.attachShader( this.glProgram, this._vsShader );
			this.context.gl.attachShader( this.glProgram, this._psShader );
			this.context.gl.linkProgram( this.glProgram );
			
			if ( ! this.context.gl.getProgramParameter( this.glProgram, this.context.gl.LINK_STATUS ) )
			{
				$THROW( false, "Could not initialise shaders" );
				this.context.gl.deleteProgram( this.glProgram );
				this.glProgram = null;
				return;
			}

			this._getUniforms();
			this._getAttributes();
			
			programCache.push( { program : this.glProgram, fShader : this._psShader, vShader : this._vsShader } );
		}
	}
	,
	_getUniforms : function()
	{
		this.uniforms = {};
		var numUniforms = this.context.gl.getProgramParameter( this.glProgram, this.context.gl.ACTIVE_UNIFORMS );

		for ( var i = 0; i < numUniforms; ++ i )
		{
			var info = this.context.gl.getActiveUniform( this.glProgram, i );
			this.uniforms[info.name] =
			{
				info : info,
				location : this.context.gl.getUniformLocation( this.glProgram, info.name )
			}
			;
		}
	}
	,
	_getAttributes : function()
	{
		this.attributes = {};
		var numAttributes = this.context.gl.getProgramParameter( this.glProgram, this.context.gl.ACTIVE_ATTRIBUTES );
		this.context.gl.useProgram( this.glProgram );
		for ( var i = 0; i < numAttributes; ++ i )
		{
			var info = this.context.gl.getActiveAttrib( this.glProgram, i );
			var attribSlot = this.context.gl.getAttribLocation( this.glProgram, info.name );
			this.attributes[info.name] =
			{
				info : info,
				location : attribSlot
			};
		}
	}
	,

	/******** inherit method ********/

	_createResource : function()
	{
		this._super();

		this.glProgram = null;
		this.uniforms = {};
		this.attributes = {};

		this._vertexShaderLoaded = false;
		this._pixelShaderLoaded = false;
		this._vsShader = null;
		this._psShader = null;
	}
	,
	_destroyResource : function()
	{
		this._super();
		
		var vsShaderCache = SmitearWebGL.Resources.Shader.vsShaderCache;
		var psShaderCache = SmitearWebGL.Resources.Shader.psShaderCache;
		var programCache = SmitearWebGL.Resources.Shader.programCache;
		var p;
		
		if ( this._vsShader && -- this._vsShader.reference <= 0 )
		{
			for ( p in vsShaderCache )
			{
				if ( vsShaderCache[p] == this._vsShader )
				{
					delete vsShaderCache[p];
					break;
				}
			}
			
			this.context.gl.deleteShader( this._vsShader );
		}
		
		if ( this._psShader && -- this._psShader.reference <= 0 )
		{
			for ( p in psShaderCache )
			{
				if ( psShaderCache[p] == this._psShader )
				{
					delete psShaderCache[p];
					break;
				}
			}
			
			this.context.gl.deleteShader( this._psShader );
		}
		
		if ( this.glProgram && -- this.glProgram.reference <= 0 )
		{
			for( var i = 0; i < programCache.length; i ++ )
			{
				if( programCache[i].program == this.glProgram )
				{
					p = programCache.splice( i, 1 );
					p.program = null;
					p.fShader = null;
					p.vShader = null;
					p = null;
					break;
				}
			}
			
			this.context.gl.deleteProgram( this.glProgram );
		}

		this.glProgram = null;
		this._vsShader = null;
		this._psShader = null;

		this.uniforms = {};
		this.attributes = {};

		this._vertexShaderLoaded = false;
		this._pixelShaderLoaded = false;
	}
}
);

SmitearWebGL.Resources.Shader.vsShaderCache = {};
SmitearWebGL.Resources.Shader.psShaderCache = {};
SmitearWebGL.Resources.Shader.programCache = [];

SmitearWebGL.ShaderUniform = 
{
	color		: 0,
	ambient		: 1,
	diffuse		: 2,
	specular	: 3,
	emissive	: 4,
	shininess	: 5,
	reflect		: 6,
	alpha		: 7,
	
	lightAmbient			: 8,
	lightIntensity			: 9,
	lightSpotInnerCone		: 10,
	lightAttenuation		: 11,
	lightSpotExponent		: 12,
	lightColor				: 13,
	lightSoftness			: 14,
	lightShadowBias			: 15,
	lightCastShadows		: 16,
	lightSpotOuterCone		: 17,
	lightSpotPMatrix		: 18,
	lightDirection			: 19,
	lightPosition			: 20,
	
	layerAlpha	: 21,
	layerMatrix	: 22,
	layerHeight	: 23,
	
	texture		: 24,
	
	worldMatrix				: 25,
	worldViewMatrix			: 26,
	invWorldViewMatrix		: 27,
	normalMatrix			: 28,
	normalViewMatrix		: 29,
	viewMatrix				: 30,
	projectionMatrix		: 31,
	invPerspectiveMatrix	: 32
		
};